package top.malaoshi.jdbc;

import java.util.ArrayList;
import java.util.List;

/**
 * 解析sql
 * @author mym
 *
 */
public class TagSQLParser {
    /**
     * 是否检测sql语句
     */
    private boolean isChecked=true;
    /**
     * 原SQL
     */
    private char[] srcSQL;
    /**
     * #{} 中的关键词，放入该list
     */
    private List<String> keywords;
    /**
     * debug模式，格式化后，可以打印sql
     */
    private boolean debug=false;
    //格式化后的sql，去掉多余的空格、tab、\n
    private char[] formatSQL=null;
    private int formatSQLIndex=0;//当前位置
    private int formatSQLLen=0;
    public TagSQLParser(String sql) {
        srcSQL=sql.toCharArray();
        int len=srcSQL.length;
        if(len==0){
            throw new TagSQLSyntaxException("请传入有效的SQL!");
        }
    }
    /**
     * 给 formatBuf 填充字符
     * @param c
     */
    private void fillFormatBuf(char c){
        formatSQL[formatSQLIndex]=c;
        formatSQLIndex++;
    }
    private void format(){
        removeWhite();
    }
    /**
     * 将 tab、\t、\n、\r\n 替换成 空格
     * 将  #{ 后面的空格移除， } 前面的空格移除
     * 当多个空格时，只保留一个空格
     */
    private void removeWhite(){
        int srcLen=srcSQL.length;
        formatSQL=new char[srcLen];
        char lastC=0;
        char c=0;
        int srcIndex=0;
        do{
            c=srcSQL[srcIndex];
            srcIndex++;
            if(c=='\r'){ //如果是windows换行符 \r，就丢弃，只认 \n
                continue;
            }
            if(isSpaceTabLine(lastC) ){ //上一次是空格、tab键、\n
                if( isSpaceTabLine(c) ){ //本次是空格、tab键、\n，就不填充字符
                }else if( c == '}' ){//本次是 } ，就将上一个空格移除，并填充 }
                    formatSQLIndex--;
                    fillFormatBuf(c);
                }else{
                    fillFormatBuf(c);
                }
            }else if(lastC == '{'){ //上一次是 {
                if( isSpaceTabLine(c) ){ //本次是空格、tab键、\n，就不填充字符
                }else{
                    fillFormatBuf(c);
                }
            }else{ //上一次不是空格、tab键、\n
                if( c == '\t' || c == '\n'){ //本次是 tab键、\n，就填充空格
                    fillFormatBuf(' ');
                }else{
                    fillFormatBuf(c);
                }
            }
            lastC=c;
        }while(srcIndex<srcLen);
        formatSQLLen=formatSQLIndex;//设置 formatBuf的有效长度
        if(debug){
            System.out.println("debug---format SQL:"+new String(formatSQL,0,formatSQLLen));
        }
        formatSQLIndex=0;
    }
    /**
     * 检查语法，给出提示，规则如下：
     * 1. #后面必须跟着{，不能其他符号
     * 2. #{ 后面可以有空格、\t符号、字母，不能有其他符号，也不能有 \n
     * @return true表示正常；false表示不正常
     */
    private void check(){
        formatSQLIndex=0;
        char curChar=0;
        do{
            curChar=getFromFormatSQL();
            if( curChar == '#'){
                curChar=getFromFormatSQL();
                if( curChar!='{'){ //#后面不是{，报错
                    String error=buildError();
                    throw new TagSQLSyntaxException(error);
                }else{ //#后面是{，正常
                    curChar=getFromFormatSQL();// { 后面的字符，由于已经格式化，所以不会是  空格 或 tab
                    if( isLetter(curChar) ){ // { 后面第一个字符，必须是字母，否则报错，如：不能是 }
                        do{
                            curChar=getFromFormatSQL(); //由于已经格式化，{ 后面不会是  空格 、tab 、\n
                            if( isLetter(curChar) ){ // { 后面可以是 字母
                            }else if( curChar=='}' ){ //也可以是 }
                                break;
                            }else{ //其他符号报错
                                String error=buildError();
                                throw new TagSQLSyntaxException(error);
                            }
                        }while(formatSQLIndex<formatSQLLen);
                    }
                    else{
                        String error=buildError();
                        throw new TagSQLSyntaxException(error);
                    }
                }
            }
        }while(formatSQLIndex<formatSQLLen);
    }
    /**
     * 构建错误提示消息
     * @return
     */
    private String buildError(){
        int startPos=formatSQLIndex-8;
        int endPos=formatSQLIndex+8;
        if(startPos<0) startPos=0;
        if(endPos>formatSQLLen) endPos=formatSQLLen;
        String nearErrorInSQL=new String(formatSQL,startPos,endPos-startPos);
        String msg=" #{} 表达式错误，位置在 "+formatSQLIndex+" ，在 【"+nearErrorInSQL+"】 附近";
        return msg;
    }
    /**
     * 从 FormatSQL 取出一个字符，并执行 formatSQLIndex++
     * @return
     */
    private char getFromFormatSQL(){
        char ret=formatSQL[formatSQLIndex];
        formatSQLIndex++;
        return ret;
    }
    /**
     * 判断是否字母
     * @param c
     * @return
     */
    private boolean isLetter(char c){
        return Character.isLetter( c );
    }
    /**
     * 是否 空格、tab、换行符 \n
     * 注意：不支持  windows换行符 \r\n
     * @param c
     * @return
     */
    private boolean isSpaceTabLine(char c){
        if( c==' ' || c=='\t' || c == '\n'){
            return true;
        }else{
            return false;
        }
    }
    public void setChecked(boolean isChecked) {
        this.isChecked = isChecked;
    }
    /**
     * 解析sql，将含有 #{} 表达式的sql，将其中的字符串解析出，放入到keywords中，然后将 #{} 替换成 ?
     * @return 返回 带有 ? 的sql
     */
    public String parse(){
        format();
        if(isChecked){
            check();
        }
        int sqlPos=0;
        char[] sql=new char[formatSQLLen];
        keywords=new ArrayList<String>();
        formatSQLIndex=0;
        char cur=0;
        int sqlStartPos=0; // 截取sql开始的位置
        int tagStartPos=0; //#{的位置
        int tagEndPos=0; // }的位置
        int keywordStartPos=0; // #{} 中关键字开始的位置
        int keywordEndPos=0; // #{} 中关键字结束的位置
        do{
            cur=getFromFormatSQL();
            if( cur == '#'){ //遇到#
                cur=getFromFormatSQL();
                if(cur=='{'){ //遇到 #{
                    tagStartPos=formatSQLIndex-2; // -2，是把 #{ 给减掉
                    int len=tagStartPos-sqlStartPos;
                    System.arraycopy(formatSQL, sqlStartPos, sql, sqlPos, len);
                    sqlPos=sqlPos+len;
                    sql[sqlPos]=' ';
                    sqlPos++;
                    sql[sqlPos]='?';
                    sqlPos++;
                    sql[sqlPos]=' ';
                    sqlPos++;
                    keywordStartPos=formatSQLIndex; // #{} 中关键字开始的位置
                    do{
                        cur=getFromFormatSQL();
                        if(cur=='}'){
                            keywordEndPos=formatSQLIndex-1; // #{} 中关键字结束的位置
                            tagEndPos=formatSQLIndex;
                            sqlStartPos=formatSQLIndex;
                            String keyword=new String(formatSQL,keywordStartPos,keywordEndPos-keywordStartPos);
                            keywords.add(keyword);
                            break;
                        }
                    }while(formatSQLIndex<formatSQLLen);
                }
            }
        }while(formatSQLIndex<formatSQLLen);
        int len=formatSQLLen-sqlStartPos;
        System.arraycopy(formatSQL, sqlStartPos, sql, sqlPos, len);
        sqlPos=sqlPos+len;
        return new String(sql,0,sqlPos);
    }
    public List<String> getKeywords() {
        return keywords;
    }
    public void setDebug(boolean debug) {
        this.debug = debug;
    }

}

